using System;
using System.Collections.Generic;
using System.Linq;
using Antlr.Runtime;
using HQLAddin.Util;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using NHibernate.Hql.Ast.ANTLR;
using NHibernate.Hql.Ast.ANTLR.Tree;

namespace HQLAddin.Highlighting.Tagger
{
	public interface IErrorAnalizer
	{
		IEnumerable<ITagSpan<ErrorTag>> GetMatches(SnapshotSpan span);
	}

	public class ErrorAnalizer : IErrorAnalizer
	{
		#region IErrorAnalizer Members

		public IEnumerable<ITagSpan<ErrorTag>> GetMatches(SnapshotSpan span)
		{
			if (!span.IsInQueryTag()) return Enumerable.Empty<ITagSpan<ErrorTag>>();

			int lineNumber = span.Start.GetContainingLine().LineNumber;
			int startLinePosition = span.Start.Position;
			ITextSnapshot snapshot = span.Snapshot;

			SnapshotExtensions.QueryInfo query = snapshot.GetQueryInfoAtLine(lineNumber);
			if (query == null || string.IsNullOrEmpty(query.Query)) return Enumerable.Empty<ITagSpan<ErrorTag>>();

			IEnumerable<QueryError> recognitionExceptions = GetRecognitionExceptions(query.Query);

			return recognitionExceptions.Select(re => CreateMatch(re, query, snapshot, startLinePosition, lineNumber))
				.Where(re => re != null).ToArray();
		}

		#endregion

		private static IEnumerable<QueryError> GetRecognitionExceptions(string query)
		{
			var tokenSource = new HqlLexer(new CaseInsensitiveStringStream(query));
			var tokens = new CommonTokenStream(tokenSource);
			var parser = new HqlParser(tokens) {TreeAdaptor = new ASTTreeAdaptor()};
			try
			{
				parser.statement();

				return parser.ParseErrorHandler.GetExceptions().Select(qe => new QueryError(qe));
			}
			catch (QuerySyntaxException qse)
			{
				return new List<QueryError> {new QueryError(qse)};
			}
		}

		private static ITagSpan<ErrorTag> CreateMatch(
			QueryError recognitionException,
			SnapshotExtensions.QueryInfo queryInfo,
			ITextSnapshot snapshot,
			int startLinePosition,
			int lineNumber)
		{
			string message = recognitionException.Message;
			//ErrorList.AddError(message);

			string[] lines = queryInfo.Query.SplitInLines();
			IDictionary<int, SnapshotExtensions.FileLineReference> queryLinesToFileLines =
				SnapshotExtensions.BuildQueryLinesToFileLines(queryInfo.QueryTagLineNumber, lines, snapshot.Lines.ToArray());

			var qlToFl = queryLinesToFileLines.Values.FirstOrDefault(lineReference => lineReference.Line.LineNumber == lineNumber);
			if (qlToFl == null) return null;

			int lastLine = lines.Count() - 1;

			int startLine = Math.Max(recognitionException.Line - 1, 0);
			int endLine = recognitionException.Line > 0 ? recognitionException.Line - 1 : lastLine;

			int startIndex = Math.Max(recognitionException.CharPositionInLine, 0);
			int endIndex = recognitionException.CharPositionInLine > 0
			               	? recognitionException.CharPositionInLine + 1
							: qlToFl.Line.Length;

			SnapshotExtensions.FileLineReference errorLineInFile = queryLinesToFileLines[startLine];
			if (startLine == endLine && errorLineInFile.Line.LineNumber != lineNumber)
			{
				return null;
			}

			if (recognitionException.Token != null && !string.IsNullOrEmpty(recognitionException.Token.Text))
			{
				endIndex = startIndex + recognitionException.Token.Text.Length;
				//message = string.Format("Invalid token at '{0}'. {1}", recognitionException.Token.Text, message);
			}

			if (endIndex == -1) return null;

			var errorTag = new ErrorTag("error", message);
			var spanLenght = Math.Min(endIndex, snapshot.Length) - startIndex;
			var span = new Span(startLinePosition + startIndex + errorLineInFile.StartPosition, spanLenght);
			return new TagSpan<ErrorTag>( new SnapshotSpan(snapshot, span), errorTag);
		}

		#region Nested type: CaseInsensitiveStringStream

		internal class CaseInsensitiveStringStream : ANTLRStringStream
		{
			public CaseInsensitiveStringStream(string input)
				: base(input)
			{
			}

			public override int LA(int i)
			{
				if (i == 0)
				{
					return 0;
				}
				if (i < 0)
				{
					i++;
				}
				if (((p + i) - 1) >= n)
				{
					return -1;
				}
				return char.ToLowerInvariant(data[(p + i) - 1]);
			}
		}

		#endregion
	}

	internal class QueryError
	{
		public QueryError(RecognitionException qe)
		{
			Message = qe.Message;
			Line = qe.Line;
			Token = qe.Token;
			CharPositionInLine = qe.CharPositionInLine;
		}

		public QueryError(QuerySyntaxException qse)
		{
			Message = qse.Message;
		}

		public string Message { get; private set; }

		public int Line { get; private set; }

		public int CharPositionInLine { get; private set; }

		public IToken Token { get; private set; }
	}
}